#ifndef _H_IACOMM
#define _H_IACOMM

#define USE_SOFTWARE_SERIAL_11

#include <stdint.h>
#ifdef USE_SOFTWARE_SERIAL_11
#include <SoftwareSerial.h>
#else
#include <NewSoftSerial.h>
#endif

/** IAcomm: helper class to send data down NewSoftSerial pipe.

Header Format:

header = (H, M, C#, C#-rcvd, M-Arg-num, M-arg-crc)
  H = special seq = 0xAA
  M = Method id [0-255]
  C# = num of command sent [0-255]
  C#-recvd = num of last command recvd [0-255]
  M-Arg-num = # of M-Arg bytes (< 2^16)
  M-arg-crc = crc of M-arg bytes

  Special methods

   M=
  0. SYNC, => header = [0xAA, 0, C#-sent, C#-recvd, 0]

  Restriction: only 56 bytes of data can be sent. To fit within
  Serial buffer. TODO: unlimited data
  
Protocol:

  SYNCING:
    send sync message. 
    wait for syncResponse.

  SENDING:
    Send header + data in one chunk.
    Wait for syncResponse. if timed out, error. 

  waiting for syncResponse is done with polling the network
  and setting the peer data. (like poll()). The behavior is
  as follows:

    If peer is sending us a request, give it to handler.
    If peer is requesting a sync, respond.
    If syncResponse, record it.

  RECEIVING:
    dealt with by using a handler.
    poll() can be explicitly called.
    But also, any network call can result in the handler
    being called.

    Note: Inside handler() can't make other IAcomm calls.

**/


#define MAX_COMM_BUF_SIZE 56
typedef uint8_t IAhdrBuf[8];
typedef uint8_t IAcommBuf[MAX_COMM_BUF_SIZE];
typedef void (*IAhandler)(uint8_t methodId, uint8_t* data, uint16_t len);

class IAcomm
{

public:
  // How long to wait before sync completes
  static uint32_t SYNC_WAIT_TIME; // 2000 by default.

  // How long to wait for command response.
  static uint32_t CMD_RESPONSE_WAIT_TIME; // 200 by default.


public:
  enum {SYNC_WAIT_TIME_DEF=5000};
  enum {POLL_WAIT_TIME_DEF=1000};
  enum {CMD_RESPONSE_WAIT_TIME_DEF=200};

  enum {Marker = 0xAA};
  enum Commands {
    M_SYNC	= 0,
    M_START_USER  = 1		// User methods can start from here.
  };

private:
#ifdef USE_SOFTWARE_SERIAL_11
  SoftwareSerial peer;		// Communication peer
#else
  NewSoftSerial peer;
#endif
  uint16_t speed;

  // send data
  uint8_t methodId;		// id of method
  uint8_t cmdSent;		// id/num of commands sent, start with 1.
  uint8_t cmdRcvd;		// id of commands rcvd (ie. peer's cmdSent)
  uint16_t numArgBytes;		// num bytes in method argument
  uint16_t argCRC;		// CRC of arg, can be 0 if don't care.
  uint8_t* dataBuf;		// Sending Data pointer

  // peer data
  uint8_t peerMethodId;		// id of method from peer
  uint8_t peerCmdRcvd;		// peer's command id rcvd (ie. my cmdSent)
  uint16_t peerDataBytes; 	// how much data peer has recvd.
  uint16_t peerCRC;
  IAcommBuf peerDataBuf;

  // header
  IAhdrBuf hdrBuf;		// Flattened sending header 

  // Comm state
  enum {NONE=0,SYNC,READING,WRITING} commStatus;

  // Handler
  IAhandler rcvHandler;

public:

  /**
     Constructor for Inter-Arduino Communication class.
     Wraps SoftwareSerial & provides synchronization & reliable writes.
     @param rxPin receive pin
     @param txPin transmit pin
     @param speed SoftwareSerial speed (DON'T USE ANYTHING ABOVE 38400)
     @param recvHandler handler routine for reads.
   */
  IAcomm(uint8_t rxPin, uint8_t txPin, uint16_t speed, IAhandler recvHandler);
  
  // Network methods

  /**
     invoke a remote method
     @param method remote method
     @param data   argument to method, can be a NULL pointer
     @param len    length of data, can be 0.
     @param waitMs how many ms to wait. -1 for forever, 0 not at all.
     @return if waiting, return true if got sync, false otherwise.
             if not waiting, return true always.
  */
  bool send(uint8_t method, uint8_t* data, uint16_t length,
	    int32_t waitMs=CMD_RESPONSE_WAIT_TIME_DEF);
  
  /**
     sync with network. for initialization and recovery.
     send a sync message. Wait for a response for CMD_RESPONSE_TIME
     then, after a delay of SYNC_DELAY_TIME, try again till 
     specified wait time is exhausted.
     @param waitMs how many ms to wait. -1 for forever, 0 not at all.
     @return return true if synced, false otherwise.
  */
  bool sync(int32_t waitMs=SYNC_WAIT_TIME_DEF);

  /**
     poll() to surrender thread to library.
     checks the network, responds to sync requests, handles requests.
     @param waitMs how many ms to wait. 0 not at all.
     @return true if there was a network activity.
  */
  bool poll(uint32_t waitMs=POLL_WAIT_TIME_DEF);
  
 private:

  void serialCheck();

  // simple write. no wait.
  inline void writeHdr2Net();
  inline bool methodIsSync() {
    return (peerMethodId == M_SYNC && peerCRC != 1);}

  inline bool userMethod() {
    return (peerMethodId != M_SYNC);}

  inline bool methodIsSyncResponse(uint8_t cmd) {
    return (peerMethodId == M_SYNC && peerCRC == 1 && peerCmdRcvd == cmd);
  }

  // send data down the wire and wait for sync response.
  bool writeData2Net(int32_t deadline);

  // set my CRC to send it down the wire.
  void CRC();

  // check peer's CRC against data.
  bool checkCRC();

  // Set the fields of class and fill up buffer
  void setData(uint8_t method, 		// method
	       uint16_t numData,	// num data arg bytes.
	       uint8_t* data,
	       bool syncResponse=false); // true if syncResponse

  // sendSync
  void sendSync();
  
  // send a response to a sync command
  void sendSyncResponse();

  // deadline can be 0 for don't wait, -1 for wait forever.
  bool waitForSyncResponse(int32_t deadline);

  // read from peer.
  bool readPeer();

  // header read.
  bool readHdr();
  
  // receive data & take care of states.
  bool recvData();

  // do the dirty work of reading bytes from peer.
  bool readData();
   
  // fill buffer from fields
  void fields2buf();

  // Set with buffer 
  bool buf2fields();	// return 0 on failure, 1 on success


}; 

  
 
#endif
